#include "TravelingSalesmanEnvironment.h"

template <typename T>
TravelingSalesmanEnvironment<T>::TravelingSalesmanEnvironment()
{
    throw Unsupported_template_exception();
}

TravelingSalesmanEnvironment<PermutationEncodedSolution>::TravelingSalesmanEnvironment()
{
    configured = false;
}

template <typename T>
void TravelingSalesmanEnvironment<T>::generateRandomTable()
{
    if (!configured)
        throw Environment_not_configured_exception();
    
    for (unsigned int i = 0; i < tripHops; ++i)
    {
        vector<int> v;
        lookupTable.push_back(v);
        
        for (unsigned int j = 0; j < tripHops; ++j)
            lookupTable[i].push_back(MyRandom::randInt(minTripLength, maxTripLength));
        
       lookupTable[i][i] = 0;
    }
    
    // The table needs to be symmetric (the trip is the same length forwards and backwards)
    for (unsigned int i = 0; i < tripHops; ++i)
    {
        for (unsigned int j = 0; j < tripHops; ++j)
            lookupTable[j][i] = lookupTable[i][j];
    }
}

template <typename T>
void TravelingSalesmanEnvironment<T>::outputTable()
{
    cout << "Lookup table: " << endl;
    
    for (unsigned int i = 0; i < lookupTable[i].size(); ++i)
    {
        for (unsigned int j = 0; j < lookupTable[j].size(); ++j)
        {
            cout << setw(5) << lookupTable[i][j] << " ";
        }
        cout << endl;
    }
}

template <typename T>
void TravelingSalesmanEnvironment<T>::readFile(string filename)
{
    ifstream fileStream;
    fileStream.open(filename.c_str());
    
    if (!fileStream.good())
        throw Invalid_file_exception();
        
    int hopLength;
    int rowCount = 0;
                
    // Initialize the lookup table
    vector<int> v;
    lookupTable.push_back(v);
                
    // Read in the data from the file into the lookup table
    while(!fileStream.eof())
    {
        if (fileStream.peek() == NEWLINE)
        {
            vector<int> v;
            lookupTable.push_back(v);
            ++rowCount;
        }
        fileStream >> hopLength;
        lookupTable[rowCount].push_back(hopLength);
    }
    fileStream.close();
    tripHops = lookupTable.size();
}

template <typename T>
void TravelingSalesmanEnvironment<T>::configure(string filename)
{
    readFile(filename);        
}

template <typename T>
void TravelingSalesmanEnvironment<T>::configure(int tripHops, int maxTripLength, int minTripLength)
{
    if (maxTripLength < minTripLength)
    {
        int temp = maxTripLength;
        minTripLength = maxTripLength;
        maxTripLength = temp;
    }
    
    if (tripHops < 2)
        throw Invalid_configuration_exception();
    
    this->tripHops = tripHops;
    this->maxTripLength = maxTripLength;
    this->minTripLength = minTripLength;
    generateRandomTable();
}

template <typename T>
void TravelingSalesmanEnvironment<T>::configure()
{
    configured = true;
    bool invalidInput = true;
    int choice;
    while (invalidInput)
    {
        cout << "How would you like to generate the lookup table?" << endl;
        cout << " (1) From a file" << endl;
        cout << " (2) Randomly" << endl;
        cin >> choice;
        
        switch(choice)
        {
            case 1: // From a file
            {
                string filename;
                
                bool fileIsNotValid = true;
                while (fileIsNotValid)
                {
	                cout << "Enter the filename: ";
	                cin >> filename;
	                try
	                {
		                readFile(filename);
	                    fileIsNotValid = false;
	                }
	                catch (Invalid_file_exception &e)
	                {
	                    cout << "ERROR: Invalid filename or path." << endl;
	                }
                }
                    
                outputTable();
                invalidInput = false;
                break;
            }
            case 2: // Randomly
            {
                cout << "How many hops do you want to generate? ";
                cin >> tripHops;
                cout << "Enter the maximum trip length: ";
                cin >> maxTripLength;
                cout << "Enter the minimum trip length: ";
                cin >> minTripLength;
                generateRandomTable();
                outputTable();
                invalidInput = false;
                break;
            }
            default:
            {
                cout << "Invalid selection, please try again" << endl;
            }
        }
    }
}

Crossover* TravelingSalesmanEnvironment<PermutationEncodedSolution>::configureCrossover()
{
    int choice = 0;
    cout << "What kind of crossover method do you wish to use?" << endl;
    cout << " (1) Single Point" << endl;
    cout << " (2) Two Point" << endl;
    cout << " (3) Random" << endl;
    
    while (true)
    {
	    cout << "? ";
	    cin >> choice;
	    
	    switch(choice)
	    {
	        case 1: 
	            return new SinglePointCrossover<PermutationEncodedSolution>();
	        case 2:
	        	return new TwoPointCrossover<PermutationEncodedSolution>();
	        case 3:
	        	return new RandomGenerationCrossover(this);
	        default: 
	        {
	        	cout << "Invalid selection." << endl;
	        	break;
	        }
	    }
    }
}


double TravelingSalesmanEnvironment<PermutationEncodedSolution>::evaluateFitness(Solution *solution)
{
    PermutationEncodedSolution* pesSolution = (PermutationEncodedSolution*) solution;
    vector<int> chromosome = pesSolution->getSolution();
    float tripLength = 0;
    float fitness = 0;
    for (unsigned int i = 0; i < (chromosome.size() - 1); ++i)
    {
        tripLength += lookupTable[chromosome[i]][chromosome[i + 1]];
    }
    
    if (tripLength != 0)
        fitness = 100.0 / tripLength;
    
    return fitness;
}

void TravelingSalesmanEnvironment<PermutationEncodedSolution>::generateRandomPopulation(vector<Solution*> &solutions, unsigned int populationSize)
{
    if (!configured)
        throw Environment_not_configured_exception();
        
    for (unsigned int i = 0; i < populationSize; ++i)
    {
        PermutationEncodedSolution *newSolution = new PermutationEncodedSolution();
        newSolution->fillWithRandomPermutation(0, tripHops - 1);
        newSolution->setFitness(evaluateFitness(newSolution));
        solutions.push_back(newSolution);
    }
}

template <typename T>
string TravelingSalesmanEnvironment<T>::getName()
{
    return "Traveling Salesman";
}

template <typename T>
TravelingSalesmanEnvironment<T>::~TravelingSalesmanEnvironment()
{
}

template class TravelingSalesmanEnvironment<PermutationEncodedSolution>;
